#include <string.h>
#include "debug.h"
#include <cerrno>
#include <pthread.h>
#include "net_tool.h"
#include <fcntl.h>
#include <iostream>
#include <string>
#include <unistd.h>
#ifdef __MINGW32__
#define SOCKLEN_T int
#else
#define SOCKLEN_T socklen_t
#endif
std::mutex inet_ntoa_mutex;
socketinf Accept(SOCKET_T serverSocket)  {
    socketinf info;
    #ifdef __MINGW32__
    SOCKADDR_IN addr;
    SOCKLEN_T len = sizeof(SOCKADDR_IN);
    info.socket_fd = accept(serverSocket, (SOCKADDR*)&addr, &len);
    if (info.socket_fd == -1) {
        throw SocketException(strerror(errno));
    }
    info.addr = addr;
    #else
    sockaddr_in addr;
    SOCKLEN_T len = sizeof(addr);
    info.socket_fd = accept(serverSocket, (sockaddr*)&addr, &len);
    if (info.socket_fd == -1) {
        throw SocketException(strerror(errno));
    }
    info.addr = addr;
    #endif
    return info;
}

void socketinf::getip(char * buf) {
    inet_ntoa_mutex.lock();
    strcpy(buf, inet_ntoa(this->addr.sin_addr));
    inet_ntoa_mutex.unlock();
}

void socketinf::close() {
    DEBUG_PRINT("close a connect");
    shutdown(this->socket_fd, SHUT_RDWR);
    #ifdef __MINGW32__
    ::closesocket(this->socket_fd);
    #else
    ::close(this->socket_fd);
    #endif
}

bool listen_server(const char * addr, unsigned short port, int * socket_fd)  {
    *socket_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(port);
    serv_addr.sin_addr.s_addr = inet_addr(addr);

    int reuse = 1;
    setsockopt(*socket_fd, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse));
    #ifdef __MINGW32__
    if ( bind(*socket_fd, (SOCKADDR*)&serv_addr, sizeof(serv_addr)) == -1) {
        throw SocketException(strerror(errno));
    }
    #else
    if ( bind(*socket_fd, (sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) {
        throw SocketException(strerror(errno));
    }
    #endif

    if ( listen(*socket_fd, 20) == -1 ){
        throw SocketException(strerror(errno));
    }
    return true;
}
#ifndef __MINGW32__
socketinf connect_to(const char * addr, unsigned short port,int timeout)  {
    SOCKET_T socket_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (socket_fd == -1) {
        throw SocketException(strerror(errno));
    }
    sockaddr_in sockAddr;
    
    SOCKLEN_T len = sizeof(sockAddr);
    memset(&sockAddr, 0, sizeof(sockAddr));
    sockAddr.sin_family = AF_INET;
    sockAddr.sin_addr.s_addr = inet_addr(addr);
    sockAddr.sin_port = htons(port);
    fcntl(socket_fd, F_SETFL, fcntl(socket_fd, F_GETFL) | O_NONBLOCK);
    int ret = connect(socket_fd, (sockaddr*)&sockAddr, len);
    socketinf inf = {socket_fd, sockAddr};
    if( ret != 0 ) {
        if(errno != EINPROGRESS) {
            throw SocketException("Connect failed");
        } else {
            struct timeval tm = {timeout,0};
            fd_set wset,rset;
            FD_ZERO(&wset);
            FD_ZERO(&rset);
            FD_SET(socket_fd, &wset);
            FD_SET(socket_fd, &rset);
            int res = select(socket_fd + 1, &rset, &wset, NULL, &tm);
            if( res < 0 ) {
                throw SocketException("Network error");
            } else if( res == 0 ) {
                throw TimeoutException();
            } else if( res == 1 ) {
                if( FD_ISSET(socket_fd, &wset) ) {
                    fcntl(socket_fd, F_SETFL, fcntl(socket_fd, F_GETFL, 0) & ~O_NONBLOCK);
                    return inf;
                }else {
                    throw SocketException("Connect failed");
                }
            }else {
                throw SocketException("Connect failed");
            }
        }
    }
    return inf;
}
#else
socketinf connect_to(const char * addr, unsigned short port,int timeout)  {
    SOCKET_T socket_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (socket_fd == -1) {
        throw SocketException(std::string("Init socket error code:") + std::to_string(WSAGetLastError()));
    }
    struct sockaddr_in sockAddr;
    memset(&sockAddr, 0, sizeof(sockAddr));
    sockAddr.sin_family = AF_INET;
    sockAddr.sin_addr.s_addr = inet_addr(addr);
    sockAddr.sin_port = htons(port);
    int ret = connect(socket_fd, (struct sockaddr*)&sockAddr, sizeof(sockAddr));
    if (ret == -1){
        std::cerr << addr << ":" << port << strerror(errno) << std::endl;
        throw SocketException("Connect failed");
    }
    socketinf inf = {socket_fd, sockAddr};
    inf.socket_fd = socket_fd;
    std::cout << "Socket:" << socket_fd << std::endl;
    return inf;
}
#endif

bool create_service(const char * addr, unsigned short port, void *(*handler_function)(void *))  {
    int* socket_fd = (int*)malloc(sizeof(int));
    pthread_t pid;
    listen_server(addr, port , socket_fd);
    pthread_create(&pid, NULL, handler_function, (void *)socket_fd);
    return true;
}

// unsigned long long ntohll(unsigned long long val) {
//     if (__BYTE_ORDER == __LITTLE_ENDIAN) {
//         return (((unsigned long long)htonl((int)((val << 32) >> 32))) << 32) | (unsigned int)htonl((int)(val >> 32));
//     } else if (__BYTE_ORDER == __BIG_ENDIAN) {
//         return val;
//     }
// }


// unsigned long long htonll(unsigned long long val) {
//     if (__BYTE_ORDER == __LITTLE_ENDIAN) {
//         return (((unsigned long long )htonl((int)((val << 32) >> 32))) << 32) | (unsigned int)htonl((int)(val >> 32));
//     } else if (__BYTE_ORDER == __BIG_ENDIAN) {
//         return val;
//     }
// }

bool isDomainName(const char * address)
{
    size_t i;
    size_t len = strlen(address);
    for(i = 0; i < len; i++)
        if( (address[i]<'0' || address[i]>'9' ) && address[i]!='.')
            return true;
    return false;
}


bool DNtoIP(const char *DN, char *ip)
{
    struct hostent *host=gethostbyname(DN);
    if(!host)
        return false;
    else {
        strcpy(ip,inet_ntoa(*(struct in_addr*)host->h_addr_list[0]));
        return true;
    }
}

namespace nettool {
    void send (SOCKET_T fd, const char * buf, ssize_t len) {
        int cnt,ret, need_send;
        cnt = ret = 0;
        while ( cnt < len ) {
            need_send = (len - cnt) > 8192 ? 8192 : (len - cnt);
            ret = ::send(fd, buf + cnt, need_send, 0);
            if (ret == -1) {
                throw SocketException(strerror(errno));
            }
            cnt += ret;
        }
    }
}